package com.xiaoxi.userserver.service.user.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaoxi.common.constant.enumeration.statuscode.system.LoginSystemEnums;
import com.xiaoxi.common.constant.enumeration.statuscode.user.RegisterUserEnums;
import com.xiaoxi.common.response.ApiResponse;
import com.xiaoxi.common.util.RSAUtils;
import com.xiaoxi.domain.password.VerifyPassword;
import com.xiaoxi.feign.checkpasswordserver.CheckPasswordServer;
import com.xiaoxi.userserver.domain.vo.user.BMSUserPasswordChangeVO;
import com.xiaoxi.userserver.mapper.user.BMSUserMapper;
import com.xiaoxi.userserver.service.user.BMSUserDetailService;
import com.xiaoxi.userserver.service.user.BMSUserPasswordChangeLogService;
import com.xiaoxi.userserver.service.user.BMSUserService;
import com.xiaoxi.common.constant.constant.TimeConstants;
import com.xiaoxi.common.constant.constant.UserCommonConstants;
import com.xiaoxi.common.constant.enumeration.statuscode.user.LoginUserEnums;
import com.xiaoxi.common.exception.BMSException;
import com.xiaoxi.common.util.JwtTokenUtils;
import com.xiaoxi.common.util.RedisUtils;
import com.xiaoxi.domain.user.BMSUserContext;
import com.xiaoxi.domain.user.BMSUser;
import com.xiaoxi.domain.user.BMSUserPasswordChangeLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;

/**
 * @ClassName BMSUserServiceImpl
 * @Description
 * @Author xiaoxi
 * @Date 2022/4/24 14:31
 * @Version 1.0
 */
@Service
@Slf4j
public class BMSUserServiceImpl extends ServiceImpl<BMSUserMapper, BMSUser> implements BMSUserService {

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private BMSUserPasswordChangeLogService bmsUserPasswordChangeLogService;

    @Resource
    private BMSUserDetailService bmsUserDetailService;

    @Resource
    private BMSUserMapper bmsUserMapper;

    @Resource
    private CheckPasswordServer checkPasswordServer;

    @Value("${bms.xiaoxi.default-password}")
    private String defaultPassword;

    @Value("${bms.xiaoxi.login.bad-password-attempts}")
    private String count;

    @Value("${bms.xiaoxi.password-check.expire-days}")
    private Long expireDays;

    @Value("${bms.xiaoxi.RSA-private-key}")
    private String rsaPrivateKey;

    @Override
    public Map<String, String> userLogin(String username, String password) throws BMSException {


        BMSUser bmsUser = authenticate(username, password);

        if (passwordEncoder.matches(defaultPassword, bmsUser.getPassword())) {
            throw new BMSException(LoginUserEnums.PASSWORD_EXPIRE);
        }

        // 看密码是否过期
        if (!checkPasswordExpire(bmsUser.getId())) {

            HashMap<String, String> tokenMap = new HashMap<>();

            // 生成Token
            BMSUserContext bmsUserContext = new BMSUserContext();
            bmsUserContext.setId(bmsUser.getId());
            bmsUserContext.setUsername(bmsUser.getUsername());
            bmsUserContext.setPassword(bmsUser.getPassword());
            String token = JwtTokenUtils.generateToken(bmsUserContext);

            tokenMap.put("token", token);
            tokenMap.put("tokenHead", "Bearer");
            tokenMap.put("username", username);

            // 修改登录时间
            bmsUserDetailService.updateLastLoginTime(bmsUser.getId());

            log.info("{} 登录成功", username);
            return tokenMap;
        }
        // 密码已过期。前往修改密码
        throw new BMSException(LoginUserEnums.PASSWORD_EXPIRE);

    }

    @Override
    public Boolean deleteUserByUserId(Long userId) {
        BMSUser bmsUser = new BMSUser();
        bmsUser.setId(userId);
        bmsUser.setDel(true);
        int num = bmsUserMapper.updateById(bmsUser);
        return num != 0;
    }

    @Override
    public BMSUser selectOneByUserName(String username) {
        QueryWrapper<BMSUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        return bmsUserMapper.selectOne(queryWrapper);
    }

    @Override
    public void changePassword(BMSUserPasswordChangeVO bmsUserPasswordChangeVO, Boolean checkOldPassword) throws BMSException {

        String username = bmsUserPasswordChangeVO.getUsername();
        String newPassword = bmsUserPasswordChangeVO.getNewPassword();
        String confirmPassword = bmsUserPasswordChangeVO.getConfirmPassword();

        try {
            newPassword = RSAUtils.decryptByPrivateKey(newPassword, rsaPrivateKey);
            confirmPassword = RSAUtils.decryptByPrivateKey(confirmPassword, rsaPrivateKey);
        } catch (Exception e) {
            log.error("密钥失效：{}", e.getMessage());
            throw new BMSException(LoginSystemEnums.PRIVATE_KEY_FAIL);
        }

        // 1. 寻找该用户名对应的是否正确
        log.info("寻找该用户名对应的是否正确");
        BMSUser bmsUser = selectOneByUserName(username);
        if (Objects.isNull(bmsUser)) {
            throw new BMSException(LoginUserEnums.USER_NOT_FOUND);
        }

        // 2. 与原密码不匹配
        if (checkOldPassword) {
            log.info("校验原密码");
            try {
                String oldPassword = RSAUtils.decryptByPrivateKey(bmsUserPasswordChangeVO.getOldPassword(), rsaPrivateKey);
                if (!passwordEncoder.matches(oldPassword, bmsUser.getPassword())) {
                    throw new BMSException(LoginUserEnums.PASSWORD_MISTAKE);
                }
            } catch (Exception e) {
                log.error("密钥失效：{}", e.getMessage());
                throw new BMSException(LoginSystemEnums.PRIVATE_KEY_FAIL);
            }
        }

        // 3. 校验密码
        log.info("校验密码复杂性");
        VerifyPassword verifyPassword = new VerifyPassword();
        verifyPassword.setUsername(username);
        verifyPassword.setNewPassword(newPassword);
        verifyPassword.setConfirmPassword(confirmPassword);
        ApiResponse apiResponse = checkPasswordServer.verifyPassword(verifyPassword);
        if(!"200".equals(apiResponse.getCode())) {
            throw new BMSException(apiResponse.getCode(),apiResponse.getMessage());
        }

        // 4. 更新内容
        log.info("更新内容");
        BMSUser user = new BMSUser();
        user.setId(bmsUser.getId());
        user.setPassword(passwordEncoder.encode(newPassword));
        user.setVersion(bmsUserPasswordChangeVO.getVersion());
        user.setAccountNonLocked(false);        // 修改了密码，就应该从冻结 解锁
        bmsUserMapper.changeUserPasswordById(user);
        bmsUserPasswordChangeLogService.updateUserPasswordChangeLog(bmsUser.getId());

        log.info("{} 修改密码成功", username);

    }

    @Override
    public BMSUser initPassword(String username, Boolean enable) throws BMSException {

        // 1. 用户名是否重复
        BMSUser bmsUser = selectOneByUserName(username);
        if (Objects.nonNull(bmsUser)) {
            throw new BMSException(RegisterUserEnums.USER_REGISTERED);
        }

        // 2. 创建用户
        BMSUser user = new BMSUser();
        user.setUsername(username);
        user.setPassword(passwordEncoder.encode(defaultPassword));  // 默认密码
        user.setEnabled(enable);
        bmsUserMapper.insert(user);

        // 3. 密码修改
        bmsUserPasswordChangeLogService.insertUserPasswordChangeLog(user.getId());

        return user;
    }

    @Override
    public BMSUser selectUserById(Long userId) {
        return bmsUserMapper.selectById(userId);
    }

    @Override
    public Boolean enableUserStatus(Long userId, Boolean enable) throws BMSException {
        BMSUser bmsUser = bmsUserMapper.selectById(userId);
        if (Objects.isNull(enable)) {
            bmsUser.setEnabled(!bmsUser.isEnabled());
        } else {
            bmsUser.setEnabled(enable);
        }
        int num = bmsUserMapper.updateById(bmsUser);
        return num != 0;
    }

    @Override
    public Boolean accountNonLockedUserStatus(Long userId, Boolean nonLockedUserStatus) throws BMSException {
        BMSUser bmsUser = bmsUserMapper.selectById(userId);
        if (Objects.isNull(nonLockedUserStatus)) {
            bmsUser.setAccountNonLocked(!bmsUser.isAccountNonLocked());
        } else {
            bmsUser.setAccountNonLocked(nonLockedUserStatus);
        }
        int num = bmsUserMapper.updateById(bmsUser);
        return num != 0;
    }

    /**
     * 认证账号、密码
     *
     * @param userUsername
     * @param userPassword
     * @return
     * @throws BMSException
     */
    private BMSUser authenticate(String userUsername, String userPassword) throws BMSException {

        BMSUser bmsUser = loadUserByUsername(userUsername);

        String sqlPassword = bmsUser.getPassword();

        // 比对密码
        if (!passwordEncoder.matches(userPassword, sqlPassword)) {

            // 当密码错误，用户有五次机会可试密码
            Integer num = (Integer) redisUtils.get(UserCommonConstants.FAIL_LOGIN_NUM + userUsername);

            if (num != null) {

                Long expireTime = redisUtils.getKeyExpire(UserCommonConstants.FAIL_LOGIN_NUM + userUsername);

                // 第N次登陆失败，超过count次被锁定
                if (num > Integer.parseInt(count)) {

                    updateUserAccountNonLocked(bmsUser);
                    redisUtils.remove(UserCommonConstants.FAIL_LOGIN_NUM + userUsername);
                    throw new BMSException(LoginUserEnums.ACCOUNT_LOCKED);

                } else {        // 低于 count 次 友善提示 错误次数

                    redisUtils.set(UserCommonConstants.FAIL_LOGIN_NUM + userUsername, ++num, expireTime);
                    throw new BMSException(LoginUserEnums.PASSWORD_MISTAKE.getCode()
                            , LoginUserEnums.PASSWORD_MISTAKE.getMessage() + "再错 " + (Integer.parseInt(count) + 1 - num) + " 次将在：" + expireTime + " 秒锁定");

                }

            } else {

                // 第一次登陆失败
                redisUtils.set(UserCommonConstants.FAIL_LOGIN_NUM + userUsername, 1, TimeConstants.REFRESH_LOGIN_TIME);
                throw new BMSException(LoginUserEnums.PASSWORD_MISTAKE);

            }

        }

        // 密码验证正确
        redisUtils.remove(UserCommonConstants.FAIL_LOGIN_NUM + userUsername);

        return bmsUser;
    }

    /**
     * 检查该用户 密码是否过期
     *
     * @return true 过期， false 未过期
     */
    private boolean checkPasswordExpire(Long userId) {
        BMSUserPasswordChangeLog bmsUserPasswordChangeLog = bmsUserPasswordChangeLogService.selectOneByUserId(userId);
        LocalDateTime end = bmsUserPasswordChangeLog.getModifyPasswordTime();
        LocalDateTime now = LocalDateTime.now();
        Duration duration = Duration.between(end, now);
        return duration.toDays() > expireDays;
    }

    /**
     * 更新 user 表。。多次登录失败，被锁定
     *
     * @param bmsUser 当前登陆
     */
    private void updateUserAccountNonLocked(BMSUser bmsUser) throws BMSException {
        bmsUser.setAccountNonLocked(true);
        int num = bmsUserMapper.updateById(bmsUser);
        if (num == 0) {
            throw new BMSException(LoginUserEnums.PLEASE_REFRESH_AND_RETRY);
        }
    }

    /**
     * 获取 数据库 - 用户信息
     * 用于认证过程中，比对用户信息
     *
     * @param username 用户名
     * @return 认证对象
     */
    public BMSUser loadUserByUsername(String username) throws BMSException {

        QueryWrapper<BMSUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        BMSUser bmsUser = bmsUserMapper.selectOne(queryWrapper);

        if (bmsUser == null) {
            throw new BMSException(LoginUserEnums.USER_NOT_FOUND);
        }

        // 1. 用户是否已删除
        if (bmsUser.getDel()) {
            throw new BMSException(LoginUserEnums.ACCOUNT_DOES_NOT_EXIST);
        }

        // 2. 用户是否锁定
        if (bmsUser.isAccountNonLocked()) {
            throw new BMSException(LoginUserEnums.ACCOUNT_LOCKED);
        }

        // 3. 用户是否停用
        if (!bmsUser.isEnabled()) {
            throw new BMSException(LoginUserEnums.USER_NOT_ENABLED);
        }

        return bmsUser;
    }

}
